package com.asen.commons.utils;

import lombok.extern.slf4j.Slf4j;

import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import java.math.BigDecimal;
import java.math.MathContext;
import java.math.RoundingMode;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.StringTokenizer;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * 四则运算的表达式逆波兰解析计算  返回数值
 *     示例：System.out.println(operator.caculateFormula("100*2-3/(-3)*5"));
 *
 * 四则运算的表达式逆波兰解析比较大小 compareTo  返回true
 *     示例：System.out.println(operator.compareTo("100*2-3/(-3)*5>=200"));
 * 支持负数,但规范除整个表达式第一个数为负数时可以不出现在'('后，其它表达式中间任何位置的负数必须出现在'('后,即：用括号括起来。比如：-3+(-2+1)*10或-3+((-2)+1)*10或(-3)+(-2+1)*10或(-3)+((-2)+1)*10
 * @author Lane
 * @version 1.0
 * @date 2020-06-13
 */
@Slf4j
public class MathExpressUtil {
    private final static String OP1 = "+";

    private final static String OP2 = "-";

    private final static String OP3 = "*";

    private final static String OP4 = "/";

    private final static String OP5 = "%";

    private final static String OPSTART = "(";

    private final static String OPEND = ")";

    private String expBase;

    private String expInited;

    private int precision = 4;

    private RoundingMode roundingMode = RoundingMode.HALF_UP;

    private MathContext mc;

    private List<String> expList = null;

    private List<String> rpnList = null;

    public MathExpressUtil() {

    }
    public MathExpressUtil(String expBase) {
        init(expBase, this.precision, this.roundingMode);
    }

    public MathExpressUtil(String expBase, int precision, RoundingMode roundingMode) {
        init(expBase, precision, roundingMode);
    }

    public String caculateFormula(String expBase) {
        return caculateFormula(expBase, precision, roundingMode);
    }

    public String caculateFormula(String expBase, int precision, RoundingMode roundingMode) {
        init(expBase, precision, roundingMode);
        return caculate();
    }

    public void init(String expBase, int precision, RoundingMode roundingMode) {
        this.expBase = expBase;
        this.precision = precision;
        this.roundingMode = roundingMode;
        this.mc = new MathContext(precision, roundingMode);
        this.expInited = initExpress(expBase);

        StringTokenizer st = new StringTokenizer(this.expInited, "+-*/^%()", true);
        this.expList = new ArrayList<String>();
        while (st.hasMoreElements()) {
            this.expList.add(st.nextElement().toString().trim());
        }

        this.rpnList = initRPN(this.expList);

    }

    public String getExpBase() {
        return expBase;
    }

    public void setExpBase(String expBase) {
        this.expBase = expBase;
    }

    public String getExpInited() {
        return expInited;
    }

    public void setExpInited(String expInited) {
        this.expInited = expInited;
    }

    public int getPrecision() {
        return precision;
    }

    public void setPrecision(int precision) {
        this.precision = precision;
    }

    public RoundingMode getRoundingMode() {
        return roundingMode;
    }

    public void setRoundingMode(RoundingMode roundingMode) {
        this.roundingMode = roundingMode;
    }

    public List<String> getExpList() {
        return expList;
    }

    public void setExpList(List<String> expList) {
        this.expList = expList;
    }

    public List<String> getRpnList() {
        return rpnList;
    }

    public void setRpnList(List<String> rpnList) {
        this.rpnList = rpnList;
    }

    public MathContext getMc() {
        return mc;
    }

    public void setMc(MathContext mc) {
        this.mc = mc;
    }

    /**
     * 去除空白字符和在负号'-'前加'0',便于后面的StringTokenizer
     *
     * @param exp
     * @return
     */
    private static String initExpress(String exp) {
        String reStr = null;
        reStr = exp.replaceAll("\\s", "");
        if (reStr.startsWith("-")) {
            reStr = "0" + reStr;
        }
        reStr = reStr.replaceAll("\\(\\-", "(0-");
        return reStr;
    }

    /**
     * 是否是整数或是浮点数,但默认-05.15这种也认为是正确的格式
     *
     * @param str
     * @return
     */
    private boolean isNumber(String str) {
        Pattern p = Pattern.compile("^(-?\\d+)(\\.\\d+)?$");
        Matcher m = p.matcher(str);
        boolean isNumber = m.matches();
        return isNumber;
    }

    /**
     * 设置优先级顺序()设置与否无所谓
     *
     * @param str
     * @return
     */
    private int precedence(String str) {
        char sign = str.charAt(0);
        switch (sign) {
            case '+':
            case '-':
                return 1;
            case '*':
            case '/':
                return 2;
            case '^':
            case '%':
                return 3;
            case '(':
            case ')':
                //          case '#':
            default:
                return 0;

        }
    }

    /**
     * 转变为逆波兰表达式
     *
     * @param strList
     * @return
     */
    public List<String> initRPN(List<String> strList) {
        List<String> returnList = new ArrayList<String>();
        //用来存放操作符的栈
        Stack stack = new Stack();
        //      stack.push(LOWESTSING);
        int length = strList.size();
        for (int i = 0; i < length; i++) {
            String str = strList.get(i);
            if (isNumber(str)) {
                returnList.add(str);
            } else {
                if (str.equals(OPSTART)) {
                    //'('直接入栈
                    stack.push(str);
                } else if (str.equals(OPEND)) {
                    //')'
                    //进行出栈操作，直到栈为空或者遇到第一个左括号
                    while (!stack.isEmpty()) {
                        //将栈顶字符串做出栈操作
                        String tempC = stack.pop();
                        if (!tempC.equals(OPSTART)) {
                            //如果不是左括号，则将字符串直接放到逆波兰链表的最后
                            returnList.add(tempC);
                        } else {
                            //如果是左括号，退出循环操作
                            break;
                        }
                    }
                } else {
                    if (stack.isEmpty()) {
                        //如果栈内为空
                        //将当前字符串直接压栈
                        stack.push(str);
                    } else {
                        //栈不空,比较运算符优先级顺序
                        if (precedence(stack.top()) >= precedence(str)) {
                            //如果栈顶元素优先级大于当前元素优先级则
                            while (!stack.isEmpty() && precedence(stack.top()) >= precedence(str)) {
                                returnList.add(stack.pop());
                            }
                        }
                        stack.push(str);
                    }
                }
            }
        }
        //如果栈不为空，则将栈中所有元素出栈放到逆波兰链表的最后
        while (!stack.isEmpty()) {
            returnList.add(stack.pop());
        }
        return returnList;
    }

    /**
     * 计算逆波兰表达式
     *
     * @param rpnList
     * @return
     */
    public String caculate(List<String> rpnList) {
        Stack numberStack = new Stack();

        int length = rpnList.size();
        for (int i = 0; i < length; i++) {
            String temp = rpnList.get(i);
            if (isNumber(temp)) {
                numberStack.push(temp);
            } else {
//				BigDecimal tempNumber1 = new BigDecimal(numberStack.pop(), this.mc);
                BigDecimal tempNumber1 = new BigDecimal(numberStack.pop());
//				BigDecimal tempNumber2 = new BigDecimal(numberStack.pop(), this.mc);
                BigDecimal tempNumber2 = new BigDecimal(numberStack.pop());
                BigDecimal tempNumber = new BigDecimal("0", this.mc);

                if (temp.equals(OP1)) {
                    tempNumber = tempNumber2.add(tempNumber1);
                } else if (temp.equals(OP2)) {
                    tempNumber = tempNumber2.subtract(tempNumber1);
                } else if (temp.equals(OP3)) {
                    tempNumber = tempNumber2.multiply(tempNumber1);
                } else if (temp.equals(OP4)) {
                    tempNumber = tempNumber2.divide(tempNumber1, precision, roundingMode);
                } else if (temp.equals(OP5)) {
                    tempNumber = tempNumber2.divideAndRemainder(tempNumber1)[1];
                }
                numberStack.push(tempNumber.toString());
            }
        }

        return numberStack.pop();

    }

    /**
     * 按照类的缺省参数进行计算
     *
     * @return
     */
    public String caculate() {
        return caculate(this.rpnList);
    }

    /**
     * 数字条件表达式精确比较
     * eg: "3.0>2"   "1<5"   "1==5"   "1!=5"   "(1.0+2)>3"  "((-0.9+3)>=2. 1)"
     * 不支持&&,||等连接符
     *
     * @param strParm
     * @return
     */
    public Boolean compareTo(String strParm) {
        boolean reBoolean = false;
        boolean isParentheses = false;//标记是否有()括上整个字符串
        String str = initExpress(strParm);
        Pattern p = Pattern.compile("^\\([\\s\\S]*\\)$");
        Matcher m = p.matcher(str);
        isParentheses = m.matches();
        if (-1 == str.indexOf(">=") && -1 == str.indexOf("<=") && -1 == str.indexOf("==") && -1 == str.indexOf("!=")) {
            if (-1 == str.indexOf(">") && -1 == str.indexOf("<"))
                throw new IllegalArgumentException("异常:条件表达式不正确!");
        }
        if (-1 != str.indexOf(">=")) {
            String[] strTemps = str.split(">=");
            if (isParentheses) {
                strTemps[0] = strTemps[0] + ")";
                strTemps[1] = "(" + strTemps[1];
            }
            int r = new BigDecimal((new MathExpressUtil(strTemps[0]).caculate())).compareTo(new BigDecimal((new MathExpressUtil(strTemps[1]).caculate())));
            if (-1 == r) {
                reBoolean = false;
            } else {
                reBoolean = true;
            }
        } else if (-1 != str.indexOf("<=")) {
            String[] strTemps = str.split("<=");
            if (isParentheses) {
                strTemps[0] = strTemps[0] + ")";
                strTemps[1] = "(" + strTemps[1];
            }
            int r = new BigDecimal((new MathExpressUtil(strTemps[0]).caculate())).compareTo(new BigDecimal((new MathExpressUtil(strTemps[1]).caculate())));
            if (1 == r) {
                reBoolean = false;
            } else {
                reBoolean = true;
            }
        } else if (-1 != str.indexOf("==")) {
            String[] strTemps = str.split("==");
            if (isParentheses) {
                strTemps[0] = strTemps[0] + ")";
                strTemps[1] = "(" + strTemps[1];
            }
            int r = new BigDecimal((new MathExpressUtil(strTemps[0]).caculate())).compareTo(new BigDecimal((new MathExpressUtil(strTemps[1]).caculate())));
            if (0 == r) {
                reBoolean = true;
            } else {
                reBoolean = false;
            }
        } else if (-1 != str.indexOf("!=")) {
            String[] strTemps = str.split("!=");
            if (isParentheses) {
                strTemps[0] = strTemps[0] + ")";
                strTemps[1] = "(" + strTemps[1];
            }
            int r = new BigDecimal((new MathExpressUtil(strTemps[0]).caculate())).compareTo(new BigDecimal((new MathExpressUtil(strTemps[1]).caculate())));
            if (0 != r) {
                reBoolean = true;
            } else {
                reBoolean = false;
            }
        } else if ((-1 != str.indexOf(">")) && (-1 == str.indexOf("="))) {
            String[] strTemps = str.split(">");
            if (isParentheses) {
                strTemps[0] = strTemps[0] + ")";
                strTemps[1] = "(" + strTemps[1];
            }
            int r = new BigDecimal((new MathExpressUtil(strTemps[0]).caculate())).compareTo(new BigDecimal((new MathExpressUtil(strTemps[1]).caculate())));
            if (1 == r) {
                reBoolean = true;
            } else {
                reBoolean = false;
            }
        } else if ((-1 != str.indexOf("<")) && (-1 == str.indexOf("="))) {
            String[] strTemps = str.split("<");
            if (isParentheses) {
                strTemps[0] = strTemps[0] + ")";
                strTemps[1] = "(" + strTemps[1];
            }
            int r = new BigDecimal((new MathExpressUtil(strTemps[0]).caculate())).compareTo(new BigDecimal((new MathExpressUtil(strTemps[1]).caculate())));
            if (-1 == r) {
                reBoolean = true;
            } else {
                reBoolean = false;
            }
        }
        return reBoolean;
    }

    private class Stack {
        LinkedList<String> stackList = new LinkedList<String>();

        public Stack() {

        }

        /**
         * 入栈
         *
         * @param expression
         */
        public void push(String expression) {
            stackList.addLast(expression);
        }

        /**
         * 出栈
         *
         * @return
         */
        public String pop() {
            return stackList.removeLast();
        }

        /**
         * 栈顶元素
         *
         * @return
         */
        public String top() {
            return stackList.getLast();
        }

        /**
         * 栈是否为空
         *
         * @return
         */
        public boolean isEmpty() {
            return stackList.isEmpty();
        }

    }

    /**
     * 复杂逻辑运算
     * @param strParm
     * @return
     */
    public Boolean compareToComplex(String strParm) {
        Boolean result = false;
        MathExpressUtil operator = new MathExpressUtil();
        ScriptEngineManager manager = new ScriptEngineManager();
        ScriptEngine engine = manager.getEngineByName("js");
        String str = strParm;
        str = str.replaceAll("or", "||");
        str = str.replaceAll("and", "&&");
        str = str.replaceAll("OR", "||");
        str = str.replaceAll("AND", "&&");
        try{
            result = (Boolean)engine.eval(str);
            System.out.println("结果类型:" + result.getClass().getName() + ",计算结果:" + result);
        }catch (Exception ex){
            log.error("",ex);
        }

        return result;

    }


    /**
     * 测试
     * @param args
     */
//    public static void main(String[] args) {
//        MathExpressUtil operator = new MathExpressUtil();
//        String testRule = "10964356.70/10964356.70-0.0000*0.25>0 and 0/10964356.70-1.0000<0 and 0/10964356.70-1.0000*0.5<0";
////        String testRule = "100+2>50 or 30-2>10";
////        System.out.println(operator.caculateFormula("100*2-3/(-3)*5"));
//        System.out.println(operator.compareToComplex(testRule));
////        System.out.println(operator.caculateFormula("40+33/(-3)*5"));
//    }
}